package com.hmall.gateway.routers;

import cn.hutool.json.JSONUtil;
import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import com.hmall.common.utils.CollUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;

@Slf4j
@Component
@RequiredArgsConstructor
public class DynamicRouteLoader {


    /**
     * Nacos配置管理器，用于从Nacos获取路由规则配置。
     */
    private final NacosConfigManager nacosConfigManager;
    /**
     * 路由定义写入器，用于将从Nacos获取到的路由规则写入到Zuul的路由定义中。
     */
    private final RouteDefinitionWriter routeDefinitionWriter;
    private final String dataId = "gateway-routes.json";
    private final String group = "DEFAULT_GROUP";
    /**
     * 路由id集合，使用Set来存储，避免重复添加。
     */
    private final Set<String> routeIds = new HashSet<>();
    /**
     * 初始化路由配置监听
     * 在什么时候：容器启动时
     */
    @PostConstruct// 在bean创建之后执行
    public void initRouteConfigListener() throws NacosException {
        // 1.项目启动时，先拉取一次配置，并添加配置到配置监听器中
        String configInfo = nacosConfigManager.getConfigService()
                .getConfigAndSignListener(dataId, group, 5000, new Listener() {
                    @Override
                    public Executor getExecutor() {
                        return null;
                    }

                    @Override
                    public void receiveConfigInfo(String s) {
                        // 2.配置更新时，重新加载路由配置
                        updateConfigInfo(s);
                    }
                });
        // 3.第一次读取到配置，也需要更新路由配置
        updateConfigInfo(configInfo);
    }
    /**
     * 更新路由配置信息。
     * 该方法接收一个包含路由配置的字符串，解析该字符串并更新路由定义。
     * 使用JSONUtil将配置信息字符串转换为RouteDefinition对象的列表。
     * 遍历列表中的每个路由定义，并使用routeDefinitionWriter将每个路由定义保存到目标系统中。
     *
     * @param configInfo 包含路由配置信息的字符串。
     */
    public void updateConfigInfo(String configInfo){
        // 日志记录：开始更新路由配置
        log.debug("开始更新路由配置{}",configInfo);
        // 将配置信息字符串解析为RouteDefinition对象的列表
        // 1.解析配置信息，转化为RouteDefinition对象
        List<RouteDefinition> list = JSONUtil.toList(configInfo, RouteDefinition.class);
        // 2.删除旧的路由配置
        for (String routeId : routeIds) {
            routeDefinitionWriter.delete(Mono.just(routeId)).subscribe();
        }
        // 清空路由id集合，准备更新
        routeIds.clear();
        // 遍历列表中的每个路由定义，进行更新
        // 判断是否有路由定义，如果没有则直接返回
        if (CollUtils.isEmpty(list)) {
            return;
        }
        // 3.更新路由配置
        list.forEach(routeDefinition -> {
            // 日志记录：更新具体的路由定义
            log.info("更新路由配置：{}",routeDefinition);
            // 3.1 使用routeDefinitionWriter保存路由定义到目标系统
            /* Mono.just(routeDefinition)：将RouteDefinition对象转换为Mono对象。
             .subscribe()：订阅保存操作，确保保存操作完成。*/
            routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
            // 3.2 记录下当前的路由id，便于后续更新时删除
            routeIds.add(routeDefinition.getId());
        });
    }
}
